#include "object.h"
#include <GL/gl.h>
#include <cmath>

Object::Object(int _id) : id(_id), selected(false){}

Object::Object(int _id, Object *_obj) : id(_id), selected(false)
{
//    _obj->getMinMax(&min, &max);
//    euler = _obj->getEulerOperators();
//
//    std::vector<HalfEdge *> halfEdges;
//    std::vector<Loop *> loops;
//
//    unsigned int size = _obj->getFacesSize();
//    for(unsigned int i = 0; i < size; i++)
//    {
//        Face *_f = _obj->getFace(i);
//        Loop *_l = _f->getOutLoop();
//        std::vector<Loop *> inLoops;
//
//        faces.push_back(new Face(_f->getId(), this, loop));
//    }

    std::cout <<"Implementar contrutor de cópia!" <<std::endl;
}

Object::~Object()
{
    /**
        deletar todo a estrutura
    */
    for(unsigned int i = 0; i < faces.size(); i++)
        delete faces[i];
    for(unsigned int i = 0; i < edges.size(); i++)
        delete edges[i];
    for(unsigned int i = 0; i < vertices.size(); i++)
        delete vertices[i];

    vertices.clear();
    edges.clear();
    faces.clear();
}

void Object::delVertex(Vertex *_v)
{
    for(unsigned int i = 0; i < vertices.size(); i++)
    {
        if(vertices[i] == _v)
        {
            vertices.erase(vertices.begin() + i);
            delete _v;
            return;
        }
    }
}

void Object::delEdge(Edge *_e)
{
    for(unsigned int i = 0; i < edges.size(); i++)
    {
        if(edges[i] == _e)
        {
            edges.erase(edges.begin() + i);
            delete _e;
            return;
        }
    }
}

void Object::delFace(Face *_f)
{
    for(unsigned int i = 0; i < faces.size(); i++)
    {
        if(faces[i] == _f)
        {
            faces.erase(faces.begin() + i);
            delete _f;
            return;
        }
    }
}

void Object::translate(const Vector3D &_t)
{
    /**
        transladar dos os vertices
        atualizar min e max
    */
    for(unsigned int i = 0; i < vertices.size(); i++)
        vertices[i]->translate(_t);
    min = min + _t;
    max = max + _t;
}

void Object::rotate(double _tetha, const Vector3D &_axis, const Vector3D &_relativePosition, bool _useRelativePos)
{
    /**
        rotacionar dos os vertices
        atualizar min e max
    */

    Vector3D center = calculateCenter();
    Matrix m;
    if(_useRelativePos) m = addRotation(toRad(_tetha), _axis.normalize(), center, center - _relativePosition);
    else m = addRotation(toRad(_tetha), _axis.normalize(), center, center - center);

    for(unsigned int i = 0; i < vertices.size(); i++)
        vertices[i]->rotate(m);
    updateMinMax();
}

void Object::scale(const Vector3D &_s, const Vector3D &_relativePosition, bool _useRelativePos)
{
    /**
        escalar dos os vertices
        atualizar min e max
    */

    Vector3D center = calculateCenter();
    Matrix m;
    if(_useRelativePos) m = addScale(_s, center, center - _relativePosition);
    else m = addScale(_s, center, center - center);

    for(unsigned int i = 0; i < vertices.size(); i++)
        vertices[i]->scale(m);
    updateMinMax();
}

void Object::sweep(Face *_f, const Vector3D &_s)
{
    euler.sweep(_f, _s);
}

double Object::area() const
{
    /**
        soma da área de todas as faces
    */

    double result = 0;

    for(unsigned int i = 0; i < faces.size(); i++)
        result += faces[i]->area();

    return result;
}

void Object::draw(bool _showObjectFace, bool _showObjectEdge, bool _showObjectVertex, bool _showNormal) const
{
    /**
        desenhar as faces, vertices e/ou arestas
    */
    if(selected)
        drawBox();

    if(_showObjectFace || _showNormal)
        for(unsigned int i = 0; i < faces.size(); i++)
            faces[i]->draw(_showObjectFace, _showNormal);


    glDisable(GL_LIGHT0);
    glDisable(GL_LIGHTING);

    if(_showObjectEdge)
        for(unsigned int i = 0; i < edges.size(); i++)
            edges[i]->draw();

    if(_showObjectVertex)
    {
        glPointSize(3);
        glBegin(GL_POINTS);
            for(unsigned int i = 0; i < vertices.size(); i++)
            {
                Vector3D pos = vertices[i]->getPosition();

                Color color_;
                Color color = vertices[i]->getColor();
                if(vertices[i]->isSelected())
                {
                    glPointSize(4);
                    color_ = Color(1, 0.0, 0.0, 1);
                }
                else if( (color.red <= 0.2) && (color.green <= 0.2)  && (color.blue <= 0.2) ) color_ = Color(1, 1, 1, 1);
                else color_ = Color(0.3, 0.3, 0.3, 1);

                glColor3f(color_.red, color_.green, color_.blue);

                glVertex3dv(pos.intoVector());

            }
        glEnd();
        glPointSize(1);
    }

    glEnable(GL_LIGHT0);
    glEnable(GL_LIGHTING);
}

void Object::updateMinMax()
{
    Vector3D p = vertices[0]->getPosition();
    double minX, minY, minZ;
    double maxX, maxY, maxZ;
    minX = maxX = p.x;
    minY = maxY = p.y;
    minZ = maxZ = p.z;

    for(unsigned int i = 1; i < vertices.size(); i++)
    {
        p  = vertices[i]->getPosition();
        if (p.x < minX) minX = p.x;
        else if (p.x > maxX) maxX = p.x;
        if (p.y < minY) minY = p.y;
        else if (p.y > maxY) maxY = p.y;
        if (p.z < minZ) minZ = p.z;
        else if (p.z > maxZ) maxZ = p.z;
    }
    min = Vector3D(minX, minY, minZ);
    max = Vector3D(maxX, maxY, maxZ);
}

void Object::setColor(const Color &_color)
{
    for(unsigned int i = 0; i < vertices.size(); i++)
        vertices[i]->setColor(_color);
}

void Object::makeArc(int _step, double _tetha1, double _tetha2, double _radius, const Color &_color)
{
    //o tetha o angulo onde começa e termina
    double tethaStep = (_tetha2 - _tetha1) / _step;

    Vector3D p[_step + 1];
    double tetha = _tetha1;
    for(int i = 0; i <= _step; i++)
    {
        double thetaRad = toRad(tetha);
        double x = _radius*cos(thetaRad);
        double z = _radius*sin(thetaRad);

        tetha = tetha + tethaStep;
        p[i] = Vector3D(x, 0, z);
    }

    euler.mvfs(this, p[0], _color);

    euler.mev(this, faces[0], vertices[0], vertices[0], p[1], _color);
    for(int i = 2; i < _step + 1; i++)
    {
        euler.mev(this, faces[0], vertices[i-1], vertices[i-2], p[i], _color);
    }

    updateMinMax();
}

void Object::makePolygon(int _sides, double _radius, const Color &_color)
{
    Vector3D p[_sides];

    double step = 360.0 / _sides;
    double tetha = 0.0;
    for(int i = 0; i < _sides; i ++)
    {
        double thetaRad = toRad(tetha);
        double x = _radius*sin(thetaRad);
        double y = _radius*cos(thetaRad);

        tetha = tetha + step;
        p[i] = Vector3D(x, y, 0);
    }

    euler.mvfs(this, p[0], _color);

    euler.mev(this, faces[0], vertices[0], vertices[0], p[1], _color);
    for(int i = 2; i < _sides; i++)
    {
        euler.mev(this, faces[0], vertices[i-1], vertices[i-2], p[i], _color);
    }
    euler.mef(this, faces[0], vertices[0], vertices[_sides-1]);

    updateMinMax();
}

void Object::makeCuboid(double _sideLength, const Color &_color)
{
    double value = _sideLength / 2.0;

    Vector3D p[4];
    p[0] = Vector3D(-value, -value, -value);
    p[1] = Vector3D(value, -value, -value);
    p[2] = Vector3D(value, value, -value);
    p[3] = Vector3D(-value, value, -value);

    euler.mvfs(this, p[0], _color);

    euler.mev(this, faces[0], vertices[0], vertices[0], p[1], _color);
    for(int i = 2; i < 4; i++)
    {
        euler.mev(this, faces[0], vertices[i-1], vertices[i-2], p[i], _color);
    }
    euler.mef(this, faces[0], vertices[0], vertices[3]);

    euler.sweep(faces[1], Vector3D(0, 0, _sideLength));

    min = Vector3D(-value, -value, -value);
    max = Vector3D(value, value, value);
}

void Object::makeSphere(double _radius, const Color &_color)
{
    makeArc(10, 90, -90, _radius, _color);

    euler.rsweep(this, Vector3D(0, 0, 1), 20, Vector3D(0, 0, 0));

    min = Vector3D(-_radius, -_radius, -_radius);
    max = Vector3D(_radius, _radius, _radius);
}

void Object::makeCylinder(double _height, double _radius, const Color &_color)
{
    double value = _height / 2.0;

    makePolygon(20, _radius, _color);

    translate(Vector3D(0, 0, -value));

    euler.sweep(faces[0], Vector3D(0, 0, _height));

    min = Vector3D(-_radius, -_radius, -value);
    max = Vector3D(_radius, _radius, value);
}

void Object::makeCone(double _height, double _radius, const Color &_color)
{
    euler.mvfs(this, Vector3D(0, 0, _height), _color);

    euler.mev(this, faces[0], vertices[0], vertices[0],  Vector3D(_radius, 0, 0), _color);

    euler.rsweep(this, Vector3D(0, 0, 1), 20, Vector3D(0, 0, 0));

    min = Vector3D(-_radius, -_radius, 0);
    max = Vector3D(_radius, _radius, _height);
}

void Object::makeHole(Face *_f1, Face *_f2)
{
    std::vector<Vector3D> p1;
    std::vector<Color> p1Color;
    std::vector<Vector3D> p2;
    std::vector<Color> p2Color;
    //buraco
    HalfEdge *heRing = _f1->getOutLoop()->getHERing();
    HalfEdge *he = heRing;
    Vector3D center;
    do
    {
        Vertex *v = he->getVertex();
        Vector3D pos = v->getPosition();
        Color color = v->getColor();
        center = center + pos;
        p1.push_back(pos);
        p1Color.push_back(color);


        he = he->getNext();
    }while(he != heRing);

    unsigned int size1 = p1.size();
    center = center*(1.0/size1);


    Vertex *prev = heRing->getTwin()->getVertex();
    Vertex *last = heRing->getVertex();

    for(unsigned int i = 0; i < size1; i++)
    {
        p1[i] = (p1[i] + center)*(1.0/2.0);

        euler.mev(this, _f1, last, prev, p1[i], p1Color[i]);

        prev = last;
        last = vertices.back();
    }
    std::vector<Vertex *>::iterator first1It = vertices.end() - size1;
    Vertex *first1 = *(first1It);
    euler.mef(this, _f1, first1, last);
    euler.kemr(this, _f1, heRing->getVertex(), first1);

    //fundo do buraco
    heRing = _f2->getOutLoop()->getHERing()->getNext();
    he = heRing;
    center = Vector3D();
    do
    {
        Vertex *v = he->getVertex();
        Vector3D pos = v->getPosition();
        Color color = v->getColor();
        center = center + pos;
        p2.push_back(pos);
        p2Color.push_back(color);


        he = he->getNext();
    }while(he != heRing);

    unsigned int size2 = p2.size();
    center = center*(1.0/size2);

    double fraction = (double)size1/(double)size2;
    double count = 0;
    int before = 0;
    p2.push_back(p2.front());
    p2.erase(p2.begin());

    Face *changingFace = faces.back();
    he = euler.findHE(changingFace, first1, *(first1It + 1));
    prev = he->getNext()->getVertex();
    last = he->getVertex();

    for(unsigned int i = size2; i > 0; i--)
    {
        p2[i - 1] = (p2[i - 1] + center)*(1.0/2.0);

        euler.mev(this, changingFace, last, prev, p2[i - 1], p2Color[i - 1]);

        count += fraction;

        if(floor(count) - before >= 1)
        {
            before = floor(count);
            he = he->getNext();
            prev = he->getNext()->getVertex();
            last = he->getVertex();
        }

    }

    std::vector<Vertex *>::iterator first2It = vertices.end() - size2;

    for(unsigned int i = 0; i < size2 - 1; i++)
    {
        euler.mef(this, changingFace, *(first2It + i), *(first2It + i + 1));
    }
    euler.mef(this, changingFace, *(first2It + size2 - 1), *(first2It));

    euler.kfmrh(this, _f2, changingFace);
}

void Object::drawBox() const
{
    /**
        desenhar bouding box
    */

    glDisable(GL_LIGHT0);
    glDisable(GL_LIGHTING);

    Vector3D point[8];
    point[0] = min;
    point[1] = Vector3D(max.x, min.y, min.z);
    point[2] = Vector3D(min.x, min.y, max.z);
    point[3] = Vector3D(max.x, min.y, max.z);
    point[4] = Vector3D(min.x, max.y, min.z);
    point[5] = Vector3D(max.x, max.y, min.z);
    point[6] = Vector3D(min.x, max.y, max.z);
    point[7] = max;

    glBegin(GL_LINES);
        glColor3d(1, 0, 0);

        glVertex3dv(point[0].intoVector());
        glVertex3dv(point[1].intoVector());

        glVertex3dv(point[1].intoVector());
        glVertex3dv(point[3].intoVector());

        glVertex3dv(point[3].intoVector());
        glVertex3dv(point[2].intoVector());

        glVertex3dv(point[2].intoVector());
        glVertex3dv(point[0].intoVector());

        glVertex3dv(point[1].intoVector());
        glVertex3dv(point[5].intoVector());

        glVertex3dv(point[3].intoVector());
        glVertex3dv(point[7].intoVector());

        glVertex3dv(point[2].intoVector());
        glVertex3dv(point[6].intoVector());

        glVertex3dv(point[0].intoVector());
        glVertex3dv(point[4].intoVector());

        glVertex3dv(point[4].intoVector());
        glVertex3dv(point[5].intoVector());

        glVertex3dv(point[5].intoVector());
        glVertex3dv(point[7].intoVector());

        glVertex3dv(point[7].intoVector());
        glVertex3dv(point[6].intoVector());

        glVertex3dv(point[6].intoVector());
        glVertex3dv(point[4].intoVector());
    glEnd();


    glEnable(GL_LIGHT0);
    glEnable(GL_LIGHTING);
}

Vector3D Object::calculateCenter() const
{
    Vector3D result;
    unsigned int size = vertices.size();
    for(unsigned int i = 0; i < size; i++)
        result = result + vertices[i]->getPosition();

    return result * (1.0/size);
}





Matrix addTranslation(const Vector3D &_translationVector)
{
    Matrix translation(4,4, IDENTITY_MATRIX);

    for(int i = 0; i < 3; i++){
        translation[i][3] = _translationVector[i];
    }

    return translation;
}

Matrix addRotation(double _tetha, const Vector3D &_rotationAxis, const Vector3D &_center, const Vector3D & _relativeVector)
{
    bool translate = false;

//    if(!(fabs(_rotationAxis.length_2() - 1.0) < ERROR )) std::cout <<"Coloque o vetor de rotação unitátio! " << _rotationAxis.length_2() <<std::endl;

    Matrix result = addTranslation(-_center);

    if(_relativeVector.length_2() > ERROR)
    {
        translate = true;
        result = addTranslation(_relativeVector) * result;
    }

    Matrix rotation(4,4, IDENTITY_MATRIX);

    double u = _rotationAxis.x;
    double u2 = u*u;
    double v = _rotationAxis.y;
    double v2 = v*v;
    double w = _rotationAxis.z;
    double w2 = w * w;
    double c = cos(_tetha);
    double s = sin(_tetha);

    rotation[0][0] = u2 + ((1 - u2) * c);
    rotation[0][1] = (u * v * (1 - c)) - (w * s);
    rotation[0][2] = (u * w * (1 - c)) + (v * s);

    rotation[1][0] = (u * v * (1 - c)) + (w * s);
    rotation[1][1] = v2 + ((1 - v2) * cos(_tetha));
    rotation[1][2] = (v * w * (1 - c)) - (u * s);

    rotation[2][0] = (u * w * (1 - c)) - (v * s);
    rotation[2][1] = (v * w * (1 - c)) + (u * s);
    rotation[2][2] = w2 + ((1 - w2) * cos(_tetha));

    c = cos(-_tetha);
    s = sin(-_tetha);

    result = rotation * result;

    if(translate)
        result = addTranslation(-_relativeVector) * result;

    result = addTranslation(_center) * result;

    return result;
}


Matrix addScale(const Vector3D &_scaleVector, const Vector3D &_center, const Vector3D & _relativeVector)
{
    bool translate = false;

    Matrix result = addTranslation(-_center);

    if(_relativeVector.length_2() > ERROR)
    {
        translate = true;
        result = addTranslation(_relativeVector) * result;
    }

    Matrix scale(4,4, IDENTITY_MATRIX);

    for(int i = 0; i < 3; i++){
        scale[i][i] = _scaleVector[i];
    }

    result = scale * result;

    if(translate)
        result = addTranslation(-_relativeVector) * result;

    result = addTranslation(_center) * result;

    return result;
}

double toRad(double _x)
{
    return (_x * M_PI)/180.0;
}
